#include "particlesystem.h"
#include "resourcemanager.h"
#include "material.h"
#include "graphic.h"

#ifdef _DEBUG
#ifdef _MSC_VER
   #define new new( _CLIENT_BLOCK, __FILE__, __LINE__)
#endif
#endif // _DEBUG

namespace Engine
{
	/**********************************************************************************************************/
	REAL GetRandomMinMax( REAL fMin, REAL fMax )
	{
		REAL fRandNum = (REAL)rand() / RAND_MAX;
		return fMin + (fMax - fMin) * fRandNum;
	}
	/**********************************************************************************************************/
	Vector3<REAL> GetRandomVector()
	{
		Vector3<REAL> vector;

		// Pick a random Z between -1.0f and 1.0f.
		vector.z = GetRandomMinMax( -1.0, 1.0 );

		// Get radius of this circle
		REAL radius = sqrt(1 - vector.z * vector.z);

		// Pick a random point on a circle.
		REAL t = GetRandomMinMax( -PI, PI );

		// Compute matching X and Y for our Z.
		vector.x = cos(t) * radius;
		vector.y = sin(t) * radius;

		return vector;
	}
	/**********************************************************************************************************/
	ParticleSystem* ParticleSystemFactory::CreateInstance(const std::string& name )
	{
		if (GetParameters() != 0)
		{
			NamedParameters::const_iterator ni1 = GetParameters()->find("material");
			NamedParameters::const_iterator ni2 = GetParameters()->find("count");
			if(ni1 != GetParameters()->end() && ni2 != GetParameters()->end())
			{
				std::stringstream buf;
				unsigned int count = 0;
				buf << ni2->second;
				buf >> count;
				return new ParticleSystem(name,ni1->second,count);
			}
		}
		throw std::invalid_argument("There are no material for particle system " + name);
	}
	/**********************************************************************************************************/
	ParticleSystem::ParticleSystem(const std::string& name, const std::string& material, unsigned int maxParticles)
		:MovableObject(name)
		,m_currentTime(0)
		,m_lastUpdate(0)
		,m_oneShootMode(false)
		,m_stopEmit(false)
		,m_step(0.02)
		,m_maxParticles(maxParticles)
		,m_numToRelease(0)
		,m_releaseInterval(0)
		,m_lifeCycle(0)
		,m_airResistence(false)
		,m_velocityVar(0)
	{
		WeakUtils::UnsafeCopy(m_material, ResourceManager::GetSingleton().Create(material, "Material"));
		m_material->Load();

		m_renderable = RefPtr<ParticleRenderable,WeakPtrExtension>(new ParticleRenderable(m_material,maxParticles));

		m_boundingBox = AABB(Vector3<REAL>(-100,-100,-100),Vector3<REAL>(100,100,100));
	}
	/**********************************************************************************************************/
	ParticleSystem::~ParticleSystem()
	{
		if(!m_material.IsNull())
		{
			std::string name = m_material->GetName();
			m_material.Delete();
			ResourceManager::GetSingleton().TryRemove(name);
		}
	}
	/**********************************************************************************************************/
	const AABB& ParticleSystem::GetBoundingBox(void) const
	{
		return m_boundingBox;
	}
	/**********************************************************************************************************/
	void ParticleSystem::Accept(MovableObjectVisitor* visitor)
	{
		visitor->Visit(this);
	}
	/**********************************************************************************************************/
	unsigned int ParticleSystem::GetMaxParticles()
	{
		Lock lock(this);
		return m_maxParticles;
	}
	/**********************************************************************************************************/
	void ParticleSystem::SetNumToRelease( unsigned int numToRelease )
	{
		Lock lock(this);
		m_numToRelease = numToRelease;
	}
	/**********************************************************************************************************/
	unsigned int ParticleSystem::GetNumToRelease( void )
	{
		Lock lock(this);
		return m_numToRelease;
	}
	/**********************************************************************************************************/
	void ParticleSystem::SetReleaseInterval( REAL releaseInterval )
	{
		Lock lock(this);
		m_releaseInterval = releaseInterval;
	}
	/**********************************************************************************************************/
	REAL ParticleSystem::GetReleaseInterval()
	{
		Lock lock(this);
		return m_releaseInterval;
	}
	/**********************************************************************************************************/
	void ParticleSystem::SetLifeCycle( REAL lifeCycle )
	{
		Lock lock(this);
		m_lifeCycle = lifeCycle;
	}
	/**********************************************************************************************************/
	REAL ParticleSystem::GetLifeCycle( void )
	{
		Lock lock(this);
		return m_lifeCycle;
	}
	/**********************************************************************************************************/
	void ParticleSystem::SetVelocity( Vector3<REAL> velocity )
	{
		Lock lock(this);
		m_velocity = velocity;
	}
	/**********************************************************************************************************/
	Vector3<REAL> ParticleSystem::GetVelocity()
	{
		Lock lock(this);
		return m_velocity;
	}
	/**********************************************************************************************************/
	void ParticleSystem::SetGravity( Vector3<REAL> gravity )
	{
		Lock lock(this);
		m_gravity = gravity;
	}
	/**********************************************************************************************************/
	Vector3<REAL> ParticleSystem::GetGravity()
	{
		return m_gravity;
	}
	/**********************************************************************************************************/
	void ParticleSystem::SetWind( Vector3<REAL> wind )
	{
		Lock lock(this);
		m_wind = wind;
	}
	/**********************************************************************************************************/
	Vector3<REAL> ParticleSystem::GetWind()
	{
		Lock lock(this);
		return m_wind;
	}
	/**********************************************************************************************************/
	void ParticleSystem::SetAirResistence( bool airResistence )
	{
		Lock lock(this);
		m_airResistence = airResistence;
	}
	/**********************************************************************************************************/
	bool ParticleSystem::GetAirResistence()
	{
		Lock lock(this);
		return m_airResistence;
	}
	/**********************************************************************************************************/
	void ParticleSystem::SetVelocityVar( REAL velocityVar )
	{
		Lock lock(this);
		m_velocityVar = velocityVar;
	}
	/**********************************************************************************************************/
	REAL ParticleSystem::GetVelocityVar()
	{
		Lock lock(this);
		return m_velocityVar;
	}
	/**********************************************************************************************************/
	void ParticleSystem::AddCollisionPlane( const Plane& plane, REAL bounceFactor, CollisionResult collisionResult)
	{
		Lock lock(this);
		PSPlane cplane;
		cplane.plane = plane;
		cplane.bounceFactor = bounceFactor;
		cplane.collisionResult = collisionResult;
		m_planes.push_back(cplane);
	}
	/**********************************************************************************************************/
	void ParticleSystem::RestartParticleSystem(void)
	{
		Lock lock(this);
		// Put the particle back on the free list...
		m_activeList.clear();
		m_stopEmit = false;
	}
	/**********************************************************************************************************/
	void ParticleSystem::SetOneShootMode(bool val)
	{
		Lock lock(this);
		m_oneShootMode = val;
	}
	/**********************************************************************************************************/
	bool ParticleSystem::GetOneShootMode()
	{
		Lock lock(this);
		return m_oneShootMode;
	}
	/**********************************************************************************************************/
	void ParticleSystem::SetStartPoint(const Vector3<REAL>& pos)
	{
		Lock lock(this);
		m_startPoint = pos;
	}
	/**********************************************************************************************************/
	const Vector3<REAL>& ParticleSystem::GetStartPoint()
	{
		Lock lock(this);
		return m_startPoint;
	}
	/**********************************************************************************************************/
	void ParticleSystem::SetStepSize(REAL val)
	{
		Lock lock(this);
		m_step = val;
	}
	/**********************************************************************************************************/
	class ProcessParticle: public std::unary_function<ParticleSystem::Particle, bool>
	{
	public:
		ProcessParticle(ParticleSystem* ps, REAL time, REAL elTime)
			:m_ps(ps)
			,m_currentTime(time)
			,m_elapsedTime(elTime)
		{}

		bool operator( ) ( ParticleSystem::Particle& particle )
		{
			Vector3<REAL> oldPosition;

			// Calculate new position
			REAL timePassed  = m_currentTime - particle.initTime;

			if( timePassed >= m_ps->m_lifeCycle)
			{
				// Time is up, delete particle
				return true;
			}
			else
			{
				// Update particle position and velocity

				// Update velocity with respect to Gravity (Constant Acceleration)
				particle.curVel += m_ps->m_gravity * m_elapsedTime;

				// Update velocity with respect to Wind (Acceleration based on
				// difference of vectors)
				if( m_ps->m_airResistence == true )
					particle.curVel += (m_ps->m_wind - particle.curVel) * m_elapsedTime;

				// Finally, update position with respect to velocity
				oldPosition = particle.curPos;
				particle.curPos += particle.curVel * m_elapsedTime;

				//-----------------------------------------------------------------
				// BEGIN Checking the particle against each plane that was set up

				ParticleSystem::PlaneList::iterator plane = m_ps->m_planes.begin();

				while( plane != m_ps->m_planes.end() )
				{
					PointPos result = plane->plane.ClassifyPoint(particle.curPos);

					if( result == POINT_BEHIND_PLANE )
					{
						if( plane->collisionResult == ParticleSystem::CR_BOUNCE )
						{
							particle.curPos = oldPosition;

							//-----------------------------------------------------------------
							//
							// The new velocity vector of a particle that is bouncing off
							// a plane is computed as follows:
							//
							// Vn = (N.V) * N
							// Vt = V - Vn
							// Vp = Vt - Kr * Vn
							//
							// Where:
							//
							// .  = Dot product operation
							// N  = The normal of the plane from which we bounced
							// V  = Velocity vector prior to bounce
							// Vn = Normal force
							// Kr = The coefficient of restitution ( Ex. 1 = Full Bounce,
							//      0 = Particle Sticks )
							// Vp = New velocity vector after bounce
							//
							//-----------------------------------------------------------------

							REAL Kr = plane->bounceFactor;

							Vector3<REAL> N = Vector3<REAL>(plane->plane.a,plane->plane.b,plane->plane.c);
							N.Normalise();
							Vector3<REAL> Vn = N.DotProduct(particle.curVel) * N;
							Vector3<REAL> Vt = particle.curVel - Vn;
							Vector3<REAL> Vp = Vt - Kr * Vn;

							particle.curVel = Vp;
						}
						else if( plane->collisionResult == ParticleSystem::CR_RECYCLE )
						{
							particle.initTime -= m_ps->m_lifeCycle;
						}
						else if( plane->collisionResult == ParticleSystem::CR_STICK )
						{
							particle.curPos = oldPosition;
							particle.curVel = Vector3<REAL>::ZERO;
						}
					}

					++plane;
				}

				// END Plane Checking
				//-----------------------------------------------------------------

				return false;
			}
		}
	private:
		ParticleSystem* m_ps;
		REAL m_currentTime;
		REAL m_elapsedTime;
	};
	void ParticleSystem::Simulate()
	{
		Lock lock(this);
		if(m_step == 0)
			return;

		REAL elapsedTime = m_step;

		m_currentTime += elapsedTime;     // Update our particle system timer...

		// process active list
		m_activeList.remove_if(ProcessParticle(this, m_currentTime, elapsedTime));


		//-------------------------------------------------------------------------
		// Emit new particles in accordance to the flow rate...
		//
		// NOTE: The system operates with a finite number of particles.
		//       New particles will be created until the max amount has
		//       been reached, after that, only old particles that have
		//       been recycled can be reintialized and used again.
		//-------------------------------------------------------------------------

		if( (m_currentTime - m_lastUpdate) > m_releaseInterval)
		{
			// Reset update timing...
			m_lastUpdate = m_currentTime;

			// Emit new particles at specified flow rate...
			if(!m_stopEmit)
			{
				for( unsigned int i = 0; i < m_numToRelease; ++i )
				{
					if( m_activeList.size() < m_maxParticles)
					{
						Particle newParticle;

						// Set the attributes for our new particle...
						newParticle.curVel = m_velocity;

						if( m_velocityVar != 0.0 )
						{
							Vector3<REAL> randomVec = GetRandomVector();
							newParticle.curVel += randomVec * m_velocityVar;
						}

						newParticle.initTime  = m_currentTime;
						newParticle.curPos    = m_startPoint;

						m_activeList.push_front(newParticle);
					}
					else
					{
						if(m_oneShootMode)
							m_stopEmit = true;
					}
				}
			}
		}
	}
	/**********************************************************************************************************/
	void ParticleSystem::Update()
	{
		if(0 != GetParentNode())
			m_stableTransform = GetParentNode()->GetDerivedTransform();
		else
			m_stableTransform = Matrix4<REAL>::IDENTITY;

		m_stablePosList.assign(m_activeList.begin(),m_activeList.end());
	}
	/**********************************************************************************************************/
	void ParticleSystem::UpdateRenderQueue(RenderQueue* queue, RenderQueue* shadowsQueue, const std::vector<LightPtr>& lightList)
	{
		if(!m_activeList.empty())
		{
			m_renderable->Update(m_stablePosList, m_stableTransform);

			queue->AddRenderable(m_renderable);
		}
	}
	/**********************************************************************************************************/
	ParticleSystem::ParticleRenderable::ParticleRenderable(MaterialPtr material, unsigned int count)
		:m_transform(Matrix4<REAL>::IDENTITY)
		,m_material(material)
	{

		m_vertexDeclaration = Graphic::GetSingleton().GetRenderSystem()->CreateVertexDeclaration();
		m_vertexDeclaration->AddElement(0, 0, VET_FLOAT3, VES_POSITION);

		m_vertexData = Graphic::GetSingleton().GetRenderSystem()->CreateVertexBuffer(count, m_vertexDeclaration->GetVertexSize(0));

		m_renderData = Graphic::GetSingleton().GetRenderSystem()->CreateRenderData(count, count, m_vertexDeclaration,m_indexData,m_vertexData, PT_POINTLIST);
	}
	/**********************************************************************************************************/
	ParticleSystem::ParticleRenderable::~ParticleRenderable()
	{
		if(!m_material.IsNull())
		{
			std::string name = m_material->GetName();
			m_material.Delete();
			ResourceManager::GetSingleton().TryRemove(name);
		}
	}
	/**********************************************************************************************************/
	void ParticleSystem::ParticleRenderable::Update(const ParticlePosList& particles, const Matrix4<REAL>& transform)
	{
		m_transform = transform;
		if(particles.empty())
			return;
		struct Vertex
		{
			float x;
			float y;
			float z;
		};
		Vertex* vertexDataPtr = static_cast<Vertex*>(m_vertexData->Lock());

		memcpy(vertexDataPtr,&particles[0],sizeof(Vertex) * particles.size());

		m_vertexData->UnLock();

		m_renderData->SetStartVertex(0);
		m_renderData->SetVertexCount(particles.size());
		m_renderData->SetPrimitiveCount(particles.size());
	}
	/**********************************************************************************************************/
}
